本章首先介绍一下JMAPI,其全称为 JRockit Management API,是一个轻量级的纯Java的API,提供了在进程内访问管理特性的功能。在JRockit早起的版本中就已经存在该API,但在R28.0.0版本中,该API已经被部分废弃,前途未卜。
JMAPI时JVM内部的API,可算作是早期版本的JRockit Management Console。事实上,即使在今天,若是连接到JRockit 1.4版本的JVM实例上,使用的仍然是名为 Rockit Management Protocol(BMP)的私有协议,而协议就是使用JMAPI来堆运行时信息做收集和修改操作的。
接下来介绍几个使用JMAPI的示例,若想编译这些示例,最好使用JRockit JDK。编译时无需做特殊配置,因为所需的类都在JRockit JDK的rt.jar
包中。在编译示例的时候,也可以加上jmapi.jar
包,该包中包含了所有接口声明。jmapi.jar
包不是JDK的一部分,是由Oracle单独发行的。
使用JMAPI来完成一些小任务是非常简单的。com.bea.jvm.JVMFactory
类可以得到实现了JVM
接口的实例,通过该实例就可以访问到JVM的各个子系统了。
2008年,Oracle收购了BEA公司,而JMAPI是在此之前出现的,因此类的包名中会含有
bea
。Oracle在收购了BEA之后,将JMAPI用在其它Oracle产品和一些第三方产品上。由于JRockit R27及其之前的版本军支持JMAPI,所以为了不影响已有的产品,包名中的bea
就被保留了下来。
在下面的示例中,会在控制台上打印出系统当前的CPU负载,共打印10次,每次间隔1秒钟。
import com.bea.jvm.JVMFactory;
public class JMAPITest {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println(
String.format("CPU load is %3.2f%%",
JVMFactory.getJVM().getMachine().getCPULoad() * 100.0));
Thread.sleep(1000);
}
}
}
访问JMAPI需要对相关权限进行配置,而无法细粒度设置权限,只能"全部允许"或"全部禁止"。若是启用了安全管理器(security manager),则需要为com.bea.jvm.ManagementPermission
赋予createInstance
权限。如下所示:
grant {
permission com.bea.jvm.ManagementPermission "createInstance";
};
更多有关权限与安全控制方面的内容,请参见http://java.sun.com/j2se/1.5.0/docs/guide/security/permissions.html。
JMAPI可用于收集有关操作环境的各种信息。在下面的示例中,演示了如何通过JMAPI来获取有关网络接口方面的信息:
for (NIC nic : JVMFactory.getJVM().getMachine().getNICs()) {
System.out.println(
nic.getDescription() + " MAC:" +
nic.getMAC() + " MTU:" + nic.getMTU());
}
还可以使用JMAPI修改运行时参数。在下面的示例中,会将JRockit进程中的线程都绑定到一个CPU上:
private static void bindToFirstCPU(JVM jvm) {
Collection<CPU> cpus = jvm.getProcessAffinity();
CPU cpu = cpus.iterator().next();
Collection<CPU> oneCpu = new LinkedList<CPU>();
oneCpu.add(cpu);
jvm.suggestProcessAffinity(oneCpu);
}
上面示例所实现的功能与使用命令参数-XX:BindToCPUs
相同,可以控制CPU亲和性。参见第5章的相关内容。
其他例如暂停时间、堆大小和年轻代大小等内容,也可以进行调整:
MemorySystem ms = JVMFactory.getJVM().getMemorySystem();
ms.suggestHeapSize(1024*1024*1024);
ms.getGarbageCollector().setPauseTimeTarget(30);
ms.getGarbageCollector().setNurserySize(256*1024*1024);
某些JMAPI中的特性需要用户做一些特别设置。有些奇怪的念头,例如在JVM内存不足时,不要抛出OutOfMemoryError
错误,而是强制终止JRockit进程的运行。
ms.setExitOnOutOfMemory(true);
此外,还可以使用JMAPI做一些简单的方法分析。在下面的示例中,启用了对java.io.StringWriter#append(CharSequence)
方法分析。在每次调用该方法时,都会打印出方法的平均执行时间。
import java.io.StringWriter;
import java.lang.reflect.Method;
import com.bea.jvm.JVMFactory;
import com.bea.jvm.MethodProfileEntry;
import com.bea.jvm.ProfilingSystem;
public class MethodProfilerExample {
public static void main(String[] args) throws Exception {
String longString = generateLongString();
ProfilingSystem profiler = JVMFactory.getJVM().getProfilingSystem();
Method appendMethod = StringWriter.class.getMethod("append", CharSequence.class);
MethodProfileEntry mpe = profiler.newMethodProfileEntry(appendMethod);
mpe.setInvocationCountEnabled(true);
mpe.setTimingEnabled(true);
String total = doAppends(10000, longString);
long invocationCount = mpe.getInvocations();
long invocationTime = mpe.getTiming();
System.out.println("Did " + invocationCount + " invocations");
System.out.println("Average invocation time was " + (invocationTime * 1000.0d) / invocationCount + " microseconds");
System.out.println("Total string length " + total.length());
}
private static String doAppends(int count, String longString) {
StringWriter writer = new StringWriter();
for (int i = 0; i < count; i++) {
writer.append(longString);
}
return writer.toString();
}
private static String generateLongString() {
StringWriter sw = new StringWriter(1000);
for (int i = 0; i < 1000; i++) {
// Build a string containing the characters
// A to Z repeatedly.
sw.append((char) (i % 26 + 65));
}
return sw.toString();
}
}
上面的示例比较简单。正常情况下,分析功能本来就是启用的,因此MethodProfileEntry
中的计数器和计时信息在执行分析之前就已经保存下来了,在分析结束后也可以正常提取出来。
回忆一下第7章和第11章的内容,其中介绍的诊断命令都可以通过JMAPI实现,而且还可以直接访问DiagnosticCommand
子系统。在下面的示例中,会模拟print_object_summary
命令的实现,在控制台中打印出对象汇总信息的直方图:
import com.bea.jvm.DiagnosticCommand;
import com.bea.jvm.JVMFactory;
public class ObjectSummary {
public static void main(String[] args) throws InterruptedException {
DiagnosticCommand dc = JVMFactory.getJVM().getDiagnosticCommand();
String output = dc.execute("print_object_summary");
System.out.println(output);
}
}
最后,JMAPI可以实现类的预处理和重定义。下面的示例展示了如何在载入类的时候重定义类的字节码:
ClassLibrary cl = JVMFactory.getJVM().getClassLibrary();
cl.setClassPreProcessor(new ClassPreProcessor() {
@Override
public byte[] preProcess(ClassLoader cl, String className, byte[] arg) {
System.out.println("Pre-processing class " + className);
return transformByteCode(arg);
}
});
任意时间,只能有一个活动的预处理器。通过redefineClass
方法,还可以重定义已经载入过的类。
JMAPI的用处还有很多,篇幅所限,这里就不再介绍那些废弃的货已经不再支持的特性。更多有关JMAPI内容,请参见Oracle官方文档。